package tss;

import java.io.*;
import java.util.*;

/**
 * <p>
 * A Message represents data that may be transported on a network
 * 
 * <p>
 * The basic layout of a message is
 * <p>
 * 
 * <pre>
 *   | header1 | ... | headerN | contents
 * </pre>
 * 
 * where the set of headers may be empty
 * 
 * @author Klaus Marius Hansen, klaus.m.hansen@daimi.au.dk
 * 
 */
public class Message implements Serializable {
	private static final long serialVersionUID = 1;
	private Stack<Object> headers = new Stack<Object>();
	private Object contents;

	/**
	 * Create a message based on the provided contents and headers. The contents
	 * and headers are required to be Serializable
	 * 
	 * <p>
	 * The resulting message will then be laid out as follows:
	 * 
	 * <pre>
	 *   | headers[0] | ... | headers[headers.length-1] | contents
	 * </pre>
	 * 
	 * @param contents
	 *            the contents object
	 * @param headers
	 *            the header objects
	 */
	public Message(Object contents, Object... headers) {
		setContents(contents);
		for (int i = headers.length - 1; i >= 0; i--) {
			pushHeader(headers[i]);
		}
	}

	/**
	 * Peek the first header of the message
	 * 
	 * @return the first header of this message or null if the message has no
	 *         headers
	 */
	public Object peekHeader() {
		Object result = null;
		if (!headers.empty()) {
			result = headers.peek();
		}
		return result;
	}

	/**
	 * Push a new header onto the headers stack
	 * 
	 * @param header
	 *            header object (required to be Serializable)
	 */
	public void pushHeader(Object header) {
		headers.push(header);
	}

	/**
	 * Pop the first header from the headers stack
	 * 
	 * @return the first header object
	 */
	public Object popHeader() {
		return headers.pop();
	}

	/**
	 * 
	 * @return the contents of this message
	 */
	public Object getContents() {
		return contents;
	}

	/**
	 * Set the contents of this message
	 * 
	 * @param contents
	 *            object (required to be Serializable)
	 */
	public void setContents(Object contents) {
		this.contents = contents;
	}

	/**
	 * Convenience method. Create a byte array representing this message.
	 * fromByteArray is the inverse function
	 * 
	 * @return a byte array representing this message
	 * 
	 * @throws IOException
	 */
	public byte[] toByteArray() throws IOException {
		byte[] result = null;
		ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		ObjectOutput out = new ObjectOutputStream(bOut);
		out.writeObject(this);
		result = bOut.toByteArray();
		out.close();
		return result;
	}

	/**
	 * Convenience (Factory) method. Create a message from the bytes provided.
	 * toByteArray is the inverse of this function
	 * 
	 * @param bytes :
	 *            the byte array from which to read the serialized version of a
	 *            message
	 * 
	 * @return a message corresponding to the byte array provided
	 * 
	 * @throws IOException
	 */
	public static Message fromByteArray(byte[] bytes) throws IOException {
		Message result = null;
		try {
			ByteArrayInputStream bIn = new ByteArrayInputStream(bytes);
			ObjectInput in = new ObjectInputStream(bIn);
			result = (Message) in.readObject();
			in.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return result;
	}
}
